home
***
CD-ROM
|
disk
|
FTP
|
other
***
search
/
Turnbull China Bikeride
/
Turnbull China Bikeride - Disc 1.iso
/
ARGONET
/
PD
/
FILER
/
X-FILESRC.ZIP
/
xrecover
/
c
/
x-recover
Wrap
Text File
|
1997-01-07
|
67KB
|
2,330 lines
/* x-recover.c 0·09
* Based (partly) on x-check.c by Andy Armstrong
* Needs chunks.h from the X-files distribution
*
* © Nicholas Clark 1996/1997
*
* Version history:
*
* 0·01 28/12/96
* First version. Successfully extracts all chunks from an (intact) X-File.
* Not really tested - did get all of the Perl 5.003 source (640 files)
* Checkmap still happy afterwards 8-)
*
* 0·02 30/12/96
* Added options to specify file offset of chunktable and root directory
* (Get this from x-guess)
* Implemented chunktable. Root directory may prove more problematic.
* (Currently do entire directory in one go by malloc()ing a buffer and reading
* the lot in. I guess I'm going to have to hunt the root directory in the chunk
* table to find out how big it is (xFiles_dirHeader.size is the *number* of
* entries, rather than the (floating) size of the names))
*
* 0·03 30/12/96
* Re-wrote dir code to read directories file by file. This allows x-recover to
* 1: Cope with directories that have an incorrect size in the chunkTable
* (I have one of these I made accidentally during testing - I copied an
* x-File after the chunktable was written, but before the cached directory
* was written)
* 2: Read directories (eg the root directory) from an arbitrary disc location.
*
* 0·04 01/01/97
* Method 2 fully implemented
* Recovers all files that correlate between directories and chunktable
* Attempts to match chunkless files to chunks of the same size.
* Recovers these files.
* Recovers all so-far unrecovered chunks
* [Methods 1 and 2 still rely on an intact chunktable. If your chunktable is
* missing (and x-guess can't find it) or destroyed, you have a problem.
* It's just like trying to recover a disc with no discmap - you have to guess
* file type and size from the contents (assuming that there is some
* characteristic to recognise.
* Until someone writes a centralised file type database containing:
* mime type
* dos extension
* unix suffix
* mac type
* identification by contents (/etc/magic in Unix)
* [optionally icon]
* xrecover_guess-o-matic will have to wait.]
*
* 0·05 02/01/97
* Spellchecked the source code!
* eg snytax is now syntax
* Important lesson: Check you executables before distribution
* It did compile, but it didn't work. Fixed it
* Much now tidier if xrecover_recoverChunkChkdsk fails to read any bytes
* Tidied up "no dirhash for filename entry" check
*
* 0·06 02/01/97
* Merged x-guess as option -g
* Added déjà vu flag to stop infinite loops if the directory tree becomes
* tangled (precursor to method 3 - readDirsRaw which will have to reparent
* directories)
* Fixed -r to figure out the chunk the root dir occupies (if possible)
* (Usually 100 !) Wrote the documentation. We do documentation?
*
* 0·07 03/01/97
* Added -a and -f flags. (and implemented them!)
*
* 0·08 04/01/97
* Documentation up to date.
* Can now do options all run together - eg
* x-recover -nvv1t 1024 $.Yaffel.dump.Perl.perl5003
* (or x-recover -nvv1t1024 $.Yaffel.dump.Perl.perl5003)
* although one must have a space after the offset of -r or -t
* Fixed two bugs ( déjà vu flag and sprintf to a possible NULL pointer )
* See below for our policy on bugs.
*
* 0·09 07/01/97
* Fixed design - now believe the get the number of chunks from chunktable chunk
* 0 rather than the header if a chunktable offset is specified
*
* Bugs: We don't do bugs.
*
* None known. Please report bugs (preferably with fixes) to <bagpuss@done.net>
* If you can supply an x-File to demonstrate then this would be useful.
* Currently I'm quite happy for relevant e-mail up to 1Mb, but if
* bagpuss.done.net is up then use anonymous ftp to upload problem files.
* (Files up to 100Mb acceptable by this method. No, I'm not confusing Kb with
* Mb. If you're on Janet then you should be able to shift it to me in 7
* minutes.)
*/
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <ctype.h>
#include <stdarg.h>
#include <limits.h>
#include "chunks.h"
#ifdef __riscos
#include "kernel.h"
#include "SWIs.h"
#endif
#ifdef __GNUC__
#include "unistd.h"
#endif
#define _max(x, y) ((x) > (y) ? (x) : (y))
#define _min(x, y) ((x) < (y) ? (x) : (y))
#define _four( N ) ((((N)-1)|3)+1)
#ifndef FREE
#define FREE 0x45455246
#endif
#ifdef __riscos
static const char *xrecover_dirSep = ".";
#else
/* Should work Unix, Amiga, DOG (DOG internals don't care whether it's \ or /
* Not that I care about DOG. Well I do actually - I care that it is extant and
* still wasting people's time, and I work towards its extinction )
* "Dummies Guide to Memory Management" - Aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaargh
* *Any* decent system just works. (usually because it comes with a flat memory
* model.)
*/
static const char *xrecover_dirSep = "/";
#endif
enum {
xrecover_bufferSize = xFiles_ALLOCATIONUNIT * 64
};
/* If I do #define then I get text substitution everywhere (including functions)
* I believe that const int will allocate memory (so that the address can be
* taken)
*/
typedef union {
xFiles_header header;
xFiles_chunk chunk;
xFiles_dirHeader dirHeader;
char padding[xFiles_ALLOCATIONUNIT];
} xguess_block;
typedef struct
{
xFiles_dirEntry dirEntry;
char name[xFiles_MAXNAME+1];
/* Does xFiles_MAXNAME include the '\0' ? */
} xrecover_dirEntryWithName;
typedef enum {
xrecover_untouched = 0x00,
xrecover_touched = 0x01,
xrecover_foundChunk = 0x02,
xrecover_sizeMatches = 0x04,
xrecover_started = 0x08,
/* An attempt to recover named file has been made. Stops list iterator
* attempting to reopen an illegal filename several times.
*/
xrecover_readWhole = 0x10,
xrecover_crossLinked = 0x20,
xrecover_openFail = 0x40,
xrecover_dejaVu = 0x80, /* Flag on entering a directory, to stop
* infinite loops if directory tree has become
* tangled */
xrecover_sorted = 0x1F /* Everything hunky-dory with this file */
} xrecover_fileStatus;
typedef xrecover_fileStatus xrecover_chunkStatus;
typedef struct xrecover_filenameList_struct
{
struct xrecover_filenameList_struct *next;
xrecover_fileStatus status;
unsigned node; /* only found in dirHash */
xFiles_dirEntry dirEntry;
} xrecover_filenameListEntry;
typedef struct
{
xrecover_chunkStatus status;
/* Currently only used for the déjà vu flag.
* If it's going to be used by anything else check that every fileList
* operation correctly updates it */
xrecover_filenameListEntry *file;
unsigned long references;
/* Not entirely sure what xFiles_chunk::usage is for - maybe I should use that
* to record how many directory entries I find pointing to the chunk */
} xrecover_chunkTableCrossCheck;
typedef union {
xFiles_dirHeader dirHeader;
xFiles_dirEntry dirEntry;
xrecover_dirEntryWithName file;
} xrecover_block;
typedef enum {
xrecover_qualityEvery1024, /* Read in 1024 byte quanta. Check each quantum
* for dir signature. If dir, skip through it
* If not, append to previous file quanta */
xrecover_qualityChkdsk, /* Read in chunktable. Write out all file
* chunks */
xrecover_qualityReadDirs, /* Read in chunktable. Read in dir structure.
* Attempt to find each named file. Write out
* all orphaned file chunks */
xrecover_qualityReadDirsRaw /* As above, but run after collecting all known
* dirs, scan x-file for other dir chunks. */
} xrecover_qualityLevel;
/* This is for return values */
typedef enum {
xrecover_chunkSuccess,
xrecover_chunkDejaVu,
xrecover_chunkCantOpen,
xrecover_chunkReadFail,
xrecover_chunkWriteFail,
xrecover_chunkAttrFail
} xrecover_chunkResult;
typedef enum {
xrecover_inDirLoad,
xrecover_inDirExec,
xrecover_inDirSize,
xrecover_inDirAttr,
xrecover_inDirNameLen,
xrecover_inDirName,
xrecover_inDirUnknown
} xrecover_inDirWhere; /* The ill-fated 1024 byte block dir-streamer */
typedef struct {
BOOL valid;
unsigned long offset;
} xrecover_Offset;
/* Both these strings have the property that the printf()ed output is the same
* length as the string 8-)
* The program assumes this further on 8-(
*
* (BUG - will overflow for X-Files with > 9999 entries)
*/
static const char *chkdskString = "file%04d";
static const char *chkdskStringMS = "FILE%04d/CHK"; /* 8-) */
static const char *xrecover_FileType( signed long load, xFiles_attr attr )
{
static char buffer[10];
if( attr & xFiles_isDir )
{
strcpy( buffer, "Directory" );
}
else
{
strcpy( buffer, " " );
if( ( load >> 24 ) == -1 )
#ifdef __riscos
{
_kernel_swi_regs regs;
_kernel_oserror *err;
regs.r[0] = 18;
regs.r[2] = (int) ( load >> 8 ) & 0xFFF;
if( err = _kernel_swi( OS_FSControl, ®s, ®s ), err )
{
fprintf( stderr, "%s\n", err->errmess );
}
else
{
*( (int *) buffer ) = regs.r[2];
*( 1 + ( (int *) buffer ) ) = regs.r[3];
}
}
#else
{
sprintf( buffer, "&%03lX ", ( load >> 8 ) & 0xFFF );
}
#endif
}
return buffer;
}
static _kernel_oserror *mkdir( const char *dirname )
#ifdef __riscos
{
_kernel_swi_regs regs;
regs.r[0] = 8;
regs.r[1] = (int) dirname;
regs.r[4] = xFiles_GROWDIRBY; /* Why not? */
return _kernel_swi( OS_File, ®s, ®s );
}
#else
#error Wot no mkdir function?
Should be fun on Unix [hint system( mkdir ) ]
#endif
/* Will have to re-do this subroutine for other OSes */
static const char *xrecover_Attr( xFiles_attr attr )
{
static char buffer[8];
char *pos = buffer;
if( attr & xFiles_isDir ) *pos++ = 'D';
if( attr & xFiles_locked ) *pos++ = 'L';
if( attr & xFiles_meRead ) *pos++ = 'R';
if( attr & xFiles_meWrite ) *pos++ = 'W';
*pos++ = '/';
if( attr & xFiles_youRead ) *pos++ = 'r';
if( attr & xFiles_youWrite ) *pos++ = 'w';
*pos = '\0';
return buffer;
}
static BOOL xrecover_ReadOffset( xrecover_Offset *offset, const char *value )
{
char *end;
offset->valid = FALSE;
if( 0 == value ) return FALSE;
offset->offset = strtoul( value, &end, 10 );
if( end == value )
{
fprintf( stderr, "Failed to make sense of offset '%s'\n", value );
return FALSE;
}
offset->valid = TRUE;
return TRUE;
}
/* 2 functions from x-check */
static int power2( int x )
{
while( x && !( x & 1 ) ) x >>= 1;
return x == 1;
}
static void test( int c, const char *fmt, ... )
{
va_list ap;
va_start( ap, fmt );
if( !c )
{
vfprintf( stderr, fmt, ap );
}
va_end( ap );
}
static unsigned xrecover_chunkFromOffset( const xFiles_chunk *chunkTable,
unsigned maxChunk,
const unsigned long offset )
{
while( maxChunk-- )
{
if( chunkTable[maxChunk].usage != FREE
&& chunkTable[maxChunk].offset == offset ) return maxChunk;
}
return 0;
}
static void printChunkDir( FILE *where, const int chunkNum,
const char *pathToHere )
{
fprintf( where, "Chunk %d ", chunkNum );
if( pathToHere )
{
fprintf( where, ( *pathToHere ) ? "[directory '%s'] "
: "[root directory] ", pathToHere );
}
}
static void printChunkDirEntry( FILE *where, const int chunkNum,
const char *pathToHere, const unsigned entry,
const char *filename, const unsigned node )
{
printChunkDir( where, chunkNum, pathToHere );
fprintf( where, "entry %d ", entry );
if( filename )
{
fprintf( where, "('%s') ", filename );
}
if( node )
{
fprintf( stderr, "->[chunk %d] ", node );
}
}
static unsigned roundDown( xFiles_header *header, unsigned pos )
{
return pos - pos % header->allocationUnit;
}
static unsigned roundUp( xFiles_header *header, unsigned pos )
{
return pos - ( pos % header->allocationUnit ) + header->allocationUnit;
}
static int okOffset( xFiles_header *header, unsigned offset )
{
return ( offset % header->allocationUnit ) == 0;
}
static int hashCmp( const void *alpha, const void *omega )
{
const xFiles_dirHash *zero = alpha;
const xFiles_dirHash *inf = omega;
return (int) zero->entryPos - (int) inf->entryPos;
}
static int filenameListEntryCmp( const void *e, const void *to_the )
{
xrecover_filenameListEntry *const *pi = e;
xrecover_filenameListEntry *const *i = to_the;
return (int) (*pi)->dirEntry.size - (int) (*i)->dirEntry.size;
}
static int chunkSizeCmp( const void *plus, const void *one )
{
const xFiles_chunk *const *equals = plus;
const xFiles_chunk *const *zero = one;
/* Currently this function is only used on known non-free chunks */
return (int) (*equals)->size - (int) (*zero)->size;
}
static int chunkOffsetCmp( const void *alpha, const void *omega )
{
/* I think it was the OED which had aardvark and zygote as its first and last
* real words.
*
* (Unfortunately, all geologists know that aa is blocky lava) */
const xFiles_chunk *const *aardvark = alpha;
const xFiles_chunk *const *zygote = omega;
/* Get all free chunks at one end */
return (*aardvark)-> usage == FREE ? -1
: (int) (*aardvark)->offset - (int) (*zygote)->offset;
}
static int xrecover_xguess( const char *suspect )
{
xguess_block block;
FILE *in;
unsigned whereIsRootChunk;
long where;
if( sizeof( xguess_block ) != xFiles_ALLOCATIONUNIT )
{
fprintf( stderr,
"Awooga, awooga: sizeof( xguess_block ) != xFiles_ALLOCATIONUNIT"
"\n\t\t%d != %d\n",
sizeof( xguess_block ), xFiles_ALLOCATIONUNIT );
return 255;
}
if( 0 == ( in = fopen( suspect, "rb" ) ) )
{
fprintf( stderr, "Could not open file '%s'\n", suspect );
}
else if( 0 == fread( &block, sizeof( block ), 1, in ) )
{
fprintf( stderr, "Could not read header from file '%s'\n", suspect );
}
else
{
test( block.header.sig == xFiles_SIG,
"Missing xFiles_SIG - read %8X, should be %8X\n",
block.header.sig, xFiles_SIG );
test( block.header.hdrSize == sizeof( xFiles_header ),
"Illegal hdrSize %d\n",
block.header.hdrSize );
test( block.header.structureVersion == xFiles_STRUCTUREVERSION,
"Illegal structureVersion %d\n",
block.header.structureVersion );
test( block.header.directoryVersion == xFiles_DIRECTORYVERSION,
"Illegal directoryVersion %d\n",
block.header.directoryVersion );
test( power2( block.header.allocationUnit ),
"Illegal allocationUnit %d\n",
block.header.allocationUnit );
printf( "File '%s' header states:\n"
" Chunktable at %d\n",
suspect, block.header.chunkTable.offset );
whereIsRootChunk = block.header.chunkTable.offset
+ block.header.rootChunk * sizeof( xFiles_chunk );
if( fseek( in, whereIsRootChunk, SEEK_SET ) )
{
fprintf( stderr, "Could not seek to root chunk at %d\n",
whereIsRootChunk );
}
else if( 0 == fread( &block, sizeof( xFiles_chunk ), 1, in ) )
{
fprintf( stderr, "Could not read root directory chunk at %d\n",
whereIsRootChunk );
}
else
{
printf( "Root directory is in chunk %d, file offset %d\n",
block.header.rootChunk, block.chunk.offset );
}
}
rewind( in );
while( ( where = ftell( in ) ),
( 0 != fread( &block, sizeof( block ), 1, in ) ) )
{
if( ( block.chunk.offset == where )
&& ( block.chunk.size % sizeof( xFiles_chunk ) == 0 ) )
{
/* Smells like the chunktable if the first 'chunk' points to this
* position in the file (which is read into where before the chunktable)
* and the size of the 'chunktable' is a multiple of the size of a chunk
*/
printf( "Could be chunktable at %ld\n", where );
}
else if( ( block.dirHeader.sig == xFiles_DIRSIG )
&& ( block.dirHeader.parent == 0 ) )
{
/* Smells like the root dir if there is a dir signature ('ANDY') and
* the parent dir is 0
* [So what's wrong with using 'Nick' as a dir signature 8-) ]
*/
printf( "Could be root directory at %ld\n", where );
}
}
return 0;
}
static void xrecover_clearList( const unsigned chunkNum,
xrecover_chunkTableCrossCheck *crossCheckEntry,
xrecover_filenameListEntry *fileTree,
int verbose )
{
if( verbose > 0 )
{
printf( "Unlinking: chunk %d currently has %ld reference(s)\n", chunkNum,
crossCheckEntry->references );
}
while( fileTree )
{
if( fileTree->node == chunkNum )
{
fprintf( stderr, "Unlinking '%s' from chunk %d\n",
( (char *) &(fileTree->dirEntry) ) + sizeof( xFiles_dirEntry ),
chunkNum );
if( ( fileTree->status & ~xrecover_crossLinked ) == 0 )
{
fputs( "Wasn't cross linked", stderr );
}
/* Clear these flags */
fileTree->status &= ~( xrecover_foundChunk | xrecover_sizeMatches
| xrecover_crossLinked );
if( crossCheckEntry->references-- == 0 )
{
fprintf( stderr, "Chunk %d reference count incorrect\n", chunkNum );
crossCheckEntry->references = 0;
}
}
fileTree = fileTree->next;
}
if( verbose > 0 )
{
printf( " now has %ld reference(s)\n",
crossCheckEntry->references );
}
crossCheckEntry->status &= ~xrecover_crossLinked;
/* As it's clear it can't be cross linked */
}
static void xrecover_printList( FILE *where,
const xFiles_chunk *chunkTable,
const xrecover_chunkTableCrossCheck
*crossCheckTable,
const xrecover_filenameListEntry *fileTree,
int verbose )
{
while( fileTree )
{
if( verbose == 0 )
{
fprintf( where, "%s\n",
( (char *) &(fileTree->dirEntry) ) + sizeof( xFiles_dirEntry ) );
}
else
{
fprintf( where, "%08X %08X %8d %-7s %s %s\n", fileTree->dirEntry.load,
fileTree->dirEntry.exec, fileTree->dirEntry.size,
xrecover_Attr( fileTree->dirEntry.attr ),
xrecover_FileType( fileTree->dirEntry.load,
fileTree->dirEntry.attr ),
( (char *) &(fileTree->dirEntry) ) + sizeof( xFiles_dirEntry ) );
if( verbose > 1 )
{
if( fileTree->status & xrecover_foundChunk )
{
fprintf( where, " chunk %d ", fileTree->node );
fprintf( where,
( chunkTable[fileTree->node].size
== fileTree->dirEntry.size )
? "size=%d" : "chunk.size=%d, dirEntry.size=%d",
chunkTable[fileTree->node].size, fileTree->dirEntry.size );
if( ( chunkTable[fileTree->node].size == fileTree->dirEntry.size )
== !!( fileTree->status & xrecover_sizeMatches ) )
{
fputc( '\n', where );
}
else
{
fputs( " [incorrectly flagged]\n", where );
}
if( fileTree->status & xrecover_crossLinked )
{
if( 1 == crossCheckTable[fileTree->node].references )
{
fputs( " error - flagged as cross linked, but chunk has exactly "
"one reference\n", where );
}
else
{
fprintf( where, " cross linked: total %ld cross references\n",
crossCheckTable[fileTree->node].references );
}
}
else
{
if( 1 != crossCheckTable[fileTree->node].references )
{
fprintf( where, " error - not flagged as cross linked, chunk has"
" %ld cross references\n",
crossCheckTable[fileTree->node].references );
}
}
}
else
{
fputs( " [no chunk found]\n", where );
}
}
}
fileTree = fileTree->next;
}
}
static void
xrecover_getProblemList( xrecover_filenameListEntry *fileTree,
xrecover_filenameListEntry **problems,
unsigned numProblems )
{
xrecover_filenameListEntry **end = problems + numProblems;
while( fileTree )
{
if( ( fileTree->status & xrecover_foundChunk ) == 0 )
{
*problems = fileTree;
if( ++problems > end )
{
fputs( "Miscounted problems in the fileTree\n", stderr );
return;
}
}
fileTree = fileTree->next;
}
}
static void xrecover_freeList( xrecover_filenameListEntry **fileTree )
{
xrecover_filenameListEntry *temp;
while( *fileTree )
{
temp = (*fileTree)->next;
free( *fileTree );
*fileTree = temp;
}
}
static xrecover_chunkResult
xrecover_recoverDir( FILE *in, FILE *out, const char *victim,
const char *pathToHere,
const unsigned long start,
const unsigned chunkNum,
const xFiles_chunk *thisChunk,
const xFiles_chunk *chunkTable,
xrecover_chunkTableCrossCheck *crossCheckTable,
xrecover_filenameListEntry **fileTree,
const unsigned maxChunk, int verbose )
{
/* out != NULL for text output
* pathToHere != NULL for full recursion
* if dirHeader == 0 need to read it from file
* start points to file offset of start of directory (good for seeking)
*/
xrecover_block buffer;
xFiles_dirHeader header;
unsigned dirEntry = 0;
size_t maxSize;
unsigned long where = -1L; /* ANSI's error result from ftell() */
xFiles_dirHash *dirHash, *currentDirHash, *dirHashLimit;
unsigned bytesRead, bytesToRead;
BOOL haveHash = FALSE;
const size_t pathLen = pathToHere ? ( 1 + strlen( pathToHere )
+ strlen( xrecover_dirSep ) ) : 0;
/* One for the '\0' */
if( thisChunk == 0 && chunkTable && chunkNum )
{
/* chunk 0 is always the chunkTable, so it can never be a directory */
thisChunk = chunkTable + chunkNum;
}
if( chunkNum && crossCheckTable &&
( crossCheckTable[chunkNum].status & xrecover_dejaVu ) )
{
printChunkDir( stderr, chunkNum, pathToHere );
fputs( "Déjà vu - been to this directory before", stderr );
if( crossCheckTable[chunkNum].file )
{
fprintf( stderr, " as '%s'\n",
( (char *) &(crossCheckTable[chunkNum].file->dirEntry) )
+ sizeof( xFiles_dirEntry ) );
}
else putc( '\n', stderr );
if( out ) fclose( out );
return xrecover_chunkDejaVu;
}
rewind( in );
if( fseek( in, start, SEEK_SET ) )
{
printChunkDir( stderr, chunkNum, pathToHere );
fprintf( stderr, "could not seek to start (offset %ld)\n", start );
if( out ) fclose( out );
return xrecover_chunkReadFail;
}
if( 0 == fread( &header, sizeof( xFiles_dirHeader ), 1, in ) )
{
printChunkDir( stderr, chunkNum, pathToHere );
fputs( "could not read dirHeader\n", stderr );
if( out ) fclose( out );
return xrecover_chunkReadFail;
}
if( verbose > 1 )
{
fputs( "Directory", stdout );
/* This has to be fputs() as puts() appends '\n'
* Why are puts, fputs and *printf so inconsistent? */
if( pathToHere )
{
printf( " '%s'", *pathToHere ? pathToHere : "<Root>" );
}
printf( ": size=%d used=%d\n", header.size, header.used );
}
if( 0 == ( dirHash = malloc( sizeof( xFiles_dirHash ) * header.used ) ) )
{
printChunkDir( stderr, chunkNum, pathToHere );
fprintf( stderr, "failed to get %d bytes of memory for dir hash\n",
sizeof( xFiles_dirHash ) * header.used );
if( out ) fclose( out );
return xrecover_chunkReadFail;
}
bytesRead = fread( dirHash, sizeof( xFiles_dirHash ), header.used, in );
if( bytesRead != header.used )
{
printChunkDir( stderr, chunkNum, pathToHere );
fprintf( stderr, "could only read %d of %d dirHash(es)\n",
bytesRead, header.used );
if( out ) fclose( out );
free( dirHash );
return xrecover_chunkReadFail;
}
/* Sort into position order. As far as I can tell X-Files 0·56 keeps its
* hash sorted by position order, but this doesn't seem to be part of the
* specification, so I won't assume it */
qsort( dirHash, header.used, sizeof( xFiles_dirHash ), hashCmp );
currentDirHash = dirHash;
dirHashLimit = dirHash + header.used;
if( fseek( in, sizeof( xFiles_dirHash ) * ( header.size - header.used ),
SEEK_CUR ) )
{
printChunkDir( stderr, chunkNum, pathToHere );
fputs( "could not seek to start of dir entries\n", stderr );
if( out ) fclose( out );
free( dirHash ); /* Something says that putting goto exit; would
* reduce the possibility of bugs (in this case a
* memory leak on dirHash and an open file on out ) */
return xrecover_chunkReadFail;
}
for( ; dirEntry < header.used; dirEntry++ )
{
where = ftell( in );
if( haveHash ) currentDirHash++;
/* Used currentDirHash, so advance it one */
if( 0 == fread( &buffer, sizeof( xFiles_dirEntry ), 1, in ) )
{
printChunkDir( stderr, chunkNum, pathToHere );
fprintf( stderr, "could not read directory entry %d\n",
dirEntry );
}
else
{
if( buffer.dirEntry.nameLen > xFiles_MAXNAME )
{
printChunkDirEntry( stderr, chunkNum, pathToHere, dirEntry, 0, 0 );
fprintf( stderr, "reports nameLen as %d - truncating to %d\n",
buffer.dirEntry.nameLen, xFiles_MAXNAME );
buffer.dirEntry.nameLen = xFiles_MAXNAME;
}
bytesToRead = _four( buffer.dirEntry.nameLen + 1 );
bytesRead = fread( &(buffer.file.name), 1, bytesToRead, in );
if( bytesRead < bytesToRead )
{
/* Terminate what we got */
buffer.file.name[bytesRead] = '\0';
printChunkDirEntry( stderr, chunkNum, pathToHere, dirEntry,
buffer.file.name, 0 );
fprintf( stderr, "could not read %d bytes of filename (Got %d)\n",
bytesToRead, bytesRead );
}
while( ( currentDirHash < dirHashLimit )
&& ( currentDirHash->entryPos < ( where - start ) ) )
{
printChunkDirEntry( stderr, chunkNum, pathToHere, dirEntry, 0, 0 );
fprintf( stderr, "no filename entry for dirhash:\n"
"\t\t'%c%c%c%c' chunk %d entryPos %d\n",
currentDirHash->nameStart[0], currentDirHash->nameStart[1],
currentDirHash->nameStart[2], currentDirHash->nameStart[3],
currentDirHash->node, currentDirHash->entryPos );
currentDirHash++;
}
if( ( currentDirHash >= dirHashLimit )
|| ( currentDirHash->entryPos != ( where - start ) ) )
{
haveHash = FALSE;
printChunkDirEntry( stderr, chunkNum, pathToHere, dirEntry,
buffer.file.name, 0 );
fputs( "no dirhash for filename entry\n", stderr );
}
else
{
haveHash = TRUE;
if( *( (int *) currentDirHash ) != *( (int *) buffer.file.name ) )
{
/* Check hash == name */
printChunkDirEntry( stderr, chunkNum, pathToHere, dirEntry,
buffer.file.name, 0 );
fprintf( stderr, "does not match dirhash '%c%c%c%c'\n",
currentDirHash->nameStart[0], currentDirHash->nameStart[1],
currentDirHash->nameStart[2], currentDirHash->nameStart[3] );
}
}
where += sizeof( xFiles_dirEntry ) + bytesToRead;
/* Where we *should* be */
if( out )
{
fprintf( out, "%08X %08X %8d %-7s %s %s\n", buffer.dirEntry.load,
buffer.dirEntry.exec, buffer.dirEntry.size,
xrecover_Attr( buffer.dirEntry.attr ),
xrecover_FileType( buffer.dirEntry.load,
buffer.dirEntry.attr ),
buffer.file.name );
}
if( fileTree && pathToHere )
{
xrecover_filenameListEntry *current =
malloc( sizeof( xrecover_filenameListEntry )
+ buffer.dirEntry.nameLen + pathLen );
char *entryPath = current ? ( (char *) &(current->dirEntry) )
+ sizeof( xFiles_dirEntry )
: 0;
/* Relative path from x-file root to this entry */
if( 0 == current ) continue; /* Implicit goto the next loop */
current->dirEntry = buffer.dirEntry;
if( *pathToHere )
{
/* Make a path */
sprintf( entryPath, "%s%s%s", pathToHere, xrecover_dirSep,
buffer.file.name );
}
else
{
strcpy( entryPath, buffer.file.name );
}
current->status = xrecover_touched;
current->node = haveHash ? currentDirHash->node : 0;
if( current->node )
{
if( current->node >= maxChunk )
{
printChunkDirEntry( stderr, chunkNum, pathToHere, dirEntry,
buffer.file.name, 0 );
fprintf( stderr, "chunk %d is out of range (%d)\n", current->node,
maxChunk );
current->node = 0;
}
else
{
if( chunkTable[current->node].usage == FREE )
{
printChunkDirEntry( stderr, chunkNum, pathToHere, dirEntry,
buffer.file.name, current->node );
fputs( "is marked free\n", stderr );
current->node = 0;
}
else
{
current->status |= xrecover_foundChunk;
/* So we have a chunk... */
if( crossCheckTable[current->node].file )
{
xrecover_filenameListEntry *crossLink =
(crossCheckTable[current->node].file);
printChunkDirEntry( stderr, chunkNum, pathToHere, dirEntry,
buffer.file.name, current->node );
fprintf( stderr, "is cross linked with file '%s'\n",
( (char *) &(crossLink->dirEntry) )
+ sizeof( xFiles_dirEntry ) );
/* Eeek. But wait for it */
if( crossLink->status & xrecover_sizeMatches )
{
/* They think that they have their chunk */
if( chunkTable[current->node].size == buffer.dirEntry.size )
{
/* And so do we */
crossLink->status |= xrecover_crossLinked;
current->status |= xrecover_crossLinked
| xrecover_sizeMatches;
crossCheckTable[current->node].references += 1;
}
else
{
/* They match and we don't */
current->status &= ~xrecover_foundChunk;
fprintf( stderr, "File '%s' size %d tallies with "
"chunktable - unlinking '%s'\n",
( (char *) &(crossLink->dirEntry) )
+ sizeof( xFiles_dirEntry ),
chunkTable[current->node].size, buffer.file.name );
current->node = 0;
}
}
else
{
/* Their size doesn't match */
if( chunkTable[current->node].size == buffer.dirEntry.size )
{
/* But ours does */
current->status |= xrecover_sizeMatches;
crossCheckTable[current->node].file = current;
xrecover_clearList( current->node,
crossCheckTable + current->node,
*fileTree, verbose );
if( crossCheckTable[current->node].references )
{
fprintf( stderr, "Awooga - failed to clear the list - %ld"
" references remain",
crossCheckTable[current->node].references );
current->status |= xrecover_crossLinked;
}
else
{
crossCheckTable[current->node].references = 1;
}
}
else
{
/* Our size doesn't match either */
crossLink->status |= xrecover_crossLinked;
current->status |= xrecover_crossLinked;
crossCheckTable[current->node].references += 1;
}
}
}
else
{
/* We have the chunk all to ourselves */
if( chunkTable[current->node].size == buffer.dirEntry.size )
{
/* And the size matched 8-) */
current->status |= xrecover_sizeMatches;
}
crossCheckTable[current->node].file = current;
crossCheckTable[current->node].references = 1;
}
if( buffer.dirEntry.attr & xFiles_isDir )
{
size_t size = victim ? strlen( victim )
+ strlen( xrecover_dirSep )
+ strlen( entryPath ) + 1
: 0;
char *tempBuffer = size ? malloc( size ) : 0;
_kernel_oserror *err = 0;
if( size )
{
if( tempBuffer == 0 )
{
printChunkDirEntry( stderr, chunkNum, pathToHere, dirEntry,
buffer.file.name, current->node );
fprintf( stderr, "failed to get %d bytes of memory for dir "
"name.\nCannot create output sub-directory, so "
"will not recurse into input sub-directory\n",
size );
err = (_kernel_oserror *) -5;
/* Should address exception/abort on data transfer if anyone
* is silly enough to try to read errmess
*/
current->status |= xrecover_started;
/* Flag to stop list routine stamping all over any offending
* file that got in our way */
}
else
{
sprintf( tempBuffer, "%s%s%s", victim, xrecover_dirSep,
entryPath );
}
}
if( size && ( 0 != ( err = mkdir( tempBuffer ) ) ) )
{
printChunkDirEntry( stderr, chunkNum, pathToHere, dirEntry,
buffer.file.name, current->node );
fprintf( stderr, "%s\n", err->errmess );
}
free( tempBuffer );
if( 0 == err )
{
xrecover_recoverDir( in, out, victim,
entryPath,
chunkTable[current->node].offset,
current->node, 0, chunkTable,
crossCheckTable, fileTree,
maxChunk, verbose );
rewind( in ); /* Clear any error */
if( fseek( in, where, SEEK_SET ) )
{
printChunkDirEntry( stderr, chunkNum, pathToHere, dirEntry,
buffer.file.name, current->node );
fputs( "could not seek ready for next directory entry\n",
stderr );
if( out ) fclose( out );
free( dirHash );
current->next = *fileTree;
fileTree = ¤t;
return xrecover_chunkReadFail;
}
}
}
} /* End of having a chunk */
}
}
else
{
printChunkDir( stderr, chunkNum, pathToHere );
fprintf( stderr, "no chunk found for '%s'\n", buffer.file.name );
}
/* Add this filename to the head of the list */
current->next = *fileTree;
*fileTree = current;
}
}
}
if( where != -1L && start != -1L && thisChunk
&& ( ( maxSize = (unsigned int) ( where - start ) ) > thisChunk->size ) )
{
printChunkDir( stderr, chunkNum, pathToHere );
fprintf( stderr, "chunkTable reports size as %d, appears to be %d\n",
thisChunk->size, maxSize );
}
if( out ) fclose( out );
free( dirHash );
return xrecover_chunkSuccess;
}
static xrecover_chunkResult
xrecover_recoverArbitaryArea( FILE *in, const char *victim,
const unsigned long offset,
unsigned long size, const int verbose )
{
xrecover_chunkResult status = xrecover_chunkSuccess;
FILE *out;
rewind( in ); /* Seems to need this to reset stream after seek beyond end */
if( fseek( in, offset, SEEK_SET ) )
{
fprintf( stderr, "Could not seek to offset %ld\n", offset );
return xrecover_chunkReadFail;
}
remove( victim ); /* This will ensure the correct file type (Data)
* and that we can open R/ files */
if( 0 == ( out = fopen( victim, "wb" ) ) )
{
fprintf( stderr, "Offset %ld could not open file '%s' for output\n",
offset, victim ? victim : "<NULL POINTER>" );
return xrecover_chunkCantOpen;
}
while( size )
{
char buffer[xrecover_bufferSize];
unsigned bytesWritten;
unsigned bytesToRead = (unsigned) _min( size, sizeof( buffer ) );
unsigned bytesRead;
memset( buffer, 0, bytesToRead );
bytesRead = fread( buffer, 1, bytesToRead, in );
if( bytesRead != bytesToRead )
{
if( status == xrecover_chunkSuccess )
{
status = xrecover_chunkReadFail;
}
fprintf( stderr, "Offset %ld could not read %d bytes (Got %d)\n",
offset, bytesToRead, bytesRead );
if( bytesRead == 0 )
{
if( verbose > 0 ) puts( "Bailing out" );
break;
}
bytesRead = bytesToRead; /* Pretend that we got them */
}
{
bytesWritten = fwrite( buffer, 1, bytesRead, out );
if( bytesRead != bytesWritten )
{
fprintf( stderr, "Offset %ld, file '%s' - only wrote %d byte(s) out of "
"%d\n", offset, victim, bytesWritten, bytesRead );
if( status == xrecover_chunkSuccess )
{
status = xrecover_chunkWriteFail;
}
}
size -= bytesRead;
}
}
fclose( out );
return status;
}
static xrecover_chunkResult
xrecover_recoverChunkChkdsk( FILE *in, const char *victim, unsigned chunkNum,
const xFiles_chunk *chunk, unsigned forceSize,
BOOL dirsAsText, BOOL suppressFiles, int verbose )
/* Leave forceSize as 0 to believe the chunkTable entry
* Otherwise, you take your life into your own hands!
*/
{
FILE *out = 0;
char buffer[xrecover_bufferSize];
unsigned bytesToCopy = forceSize ? forceSize : chunk->size;
unsigned bytesRead, bytesWritten;
unsigned bytesToRead = _min( bytesToCopy, sizeof( buffer ) );
BOOL isDir = FALSE;
xrecover_chunkResult status = xrecover_chunkSuccess; /* optimism */
rewind( in ); /* Seems to need this to reset stream after seek beyond end */
if( fseek( in, chunk->offset, SEEK_SET ) )
{
fprintf( stderr, "Chunk %d could not seek to start (offset %d)\n", chunkNum,
chunk->offset );
return xrecover_chunkReadFail;
}
/* printf( "off=%d f=%d c=%d using %d\n", chunk->offset, forceSize, chunk->size, bytesToCopy ); */
if( verbose > 3 )
{
printf( "Sought %d, got to %ld\n", chunk->offset, ftell( in ) );
}
memset( buffer, 0, sizeof( buffer ) );
bytesRead = fread( buffer, 1, bytesToRead, in );
if( bytesRead < bytesToRead )
{
fprintf( stderr, "Chunk %d could not read %d bytes (Got %d)\n",
chunkNum, sizeof( buffer ), bytesRead );
return xrecover_chunkReadFail;
}
if( *(int *) buffer == xFiles_DIRSIG && dirsAsText )
{
/* Smells like a directory. */
isDir = TRUE;
}
else if( suppressFiles ) return xrecover_chunkSuccess;
if( victim )
{
remove( victim ); /* This will ensure the correct file type (Text or Data)
* and that we can open R/ files */
out = fopen( victim, isDir ? "w": "wb" );
}
if( ( victim != 0 ) && ( out == 0 ) )
{
status = xrecover_chunkCantOpen;
fprintf( stderr, "Chunk %d could not open file '%s' for output\n", chunkNum,
victim );
}
if( isDir )
{
/* It did use to pass in the header, with code to read the header in
* xrecover_recoverDir() if a NULL pointer was passed, but I figured that
* the amount of code generated was probably greater than the time saved for
* a buffered filing system re-reading 16 bytes! */
return xrecover_recoverDir( in, out, 0, 0, chunk->offset, chunkNum, chunk,
0, 0, 0, 0, verbose );
}
if( bytesRead > bytesToCopy ) bytesRead = bytesToCopy;
/* Cope with short files */
while( bytesToCopy )
{
/* I was going to write a stream based dir converter, but then I thought:
* Sod it, let's just malloc a buffer big enough.
if( isDir )
{
}
else
*/
{
if( out )
{
bytesWritten = fwrite( buffer, 1, bytesRead, out );
if( bytesRead != bytesWritten )
{
fprintf( stderr, "Chunk %d, file '%s' - only wrote %d byte(s) out of "
"%d\n", chunkNum, victim, bytesWritten, bytesRead );
if( status == xrecover_chunkSuccess )
{
status = xrecover_chunkWriteFail;
}
}
}
}
bytesToCopy -= bytesRead;
if( bytesToCopy != 0 )
{
bytesToRead = _min( bytesToCopy, sizeof( buffer ) );
memset( buffer, 0, bytesToRead );
bytesRead = fread( buffer, 1, bytesToRead, in );
if( bytesRead != bytesToRead )
{
fprintf( stderr, "Chunk %d could not read %d bytes (Got %d)\n",
chunkNum, bytesToRead, bytesRead );
if( ferror( in ) )
{
perror( "Error on input x-file" );
}
else
{
fprintf( stderr, "Reached end of input x-file - now at %ld\n",
ftell( in ) );
}
if( status == xrecover_chunkSuccess )
{
status = xrecover_chunkReadFail;
}
if( bytesRead == 0 )
{
if( verbose > 0 ) puts( "Bailing out" );
break;
}
bytesRead = bytesToRead; /* Pretend that we got them */
}
}
}
if( out ) fclose( out );
return status;
}
static unsigned
xrecover_doGoodInList( FILE *in, const char *victim,
const xFiles_chunk *chunkTable,
xrecover_chunkTableCrossCheck *crossCheckTable,
xrecover_filenameListEntry *fileTree,
const BOOL useAlloc, const int verbose )
{
unsigned problemFiles = 0;
xrecover_chunkResult result;
size_t prefixNameLen = strlen( victim ) + strlen( xrecover_dirSep ) + 1;
while( fileTree )
{
if( fileTree->status & xrecover_foundChunk )
{
if( victim && 0 == ( fileTree->status & xrecover_started ) )
{
char *outPathname;
const xFiles_chunk *thisChunk = chunkTable + fileTree->node;
unsigned getSize = 0; /* Believe the chunk by default */
const char *filename = ( (char *) &(fileTree->dirEntry) )
+ sizeof( xFiles_dirEntry );
if( !( fileTree->status & xrecover_sizeMatches )
&& !( fileTree->dirEntry.attr & xFiles_isDir ) )
/* Already flagged problem directories
* directories report size in parent as 0, but have non-zero size in the
* chunktable, hence they will always error here */
{
/* Which has it bigger - the directory or the chunk? */
getSize = _max( fileTree->dirEntry.size, thisChunk->size );
fprintf( stderr, "File '%s' chunk.size=%d, dirEntry.size=%d "
"chunk.allocSize=%d\n"
"Will try to recover %d%s\n", filename, thisChunk->size,
fileTree->dirEntry.size, thisChunk->allocSize,
getSize, ( getSize > thisChunk->allocSize )
? " [which is greater than the allocated size]"
: "" );
}
if( useAlloc ) getSize = _max( getSize, thisChunk->allocSize );
outPathname = malloc( prefixNameLen + strlen( filename ) );
if( outPathname == 0 )
{
fprintf( stderr, "Could not get buffer for file '%s' - will add chunk"
" %d to unclaimed list\n", filename, fileTree->node );
result = xrecover_chunkCantOpen;
}
else
{
sprintf( outPathname, "%s%s%s", victim, xrecover_dirSep, filename );
/* Go for it */
result = ( fileTree->dirEntry.attr & xFiles_isDir )
? xrecover_chunkSuccess /* Directory is already done */
: xrecover_recoverChunkChkdsk( in, outPathname,
fileTree->node, thisChunk,
getSize, FALSE, FALSE,
verbose );
}
switch( result )
{
case xrecover_chunkSuccess:
case xrecover_chunkWriteFail:
/* Not my problem if your media won't write */
{
fileTree->status |= xrecover_readWhole;
#ifdef __riscos
if( 0 == ( fileTree->status & xrecover_openFail ) )
{
_kernel_swi_regs regs;
_kernel_oserror *err;
regs.r[0] = 1;
regs.r[1] = (int) outPathname;
regs.r[2] = fileTree->dirEntry.load;
regs.r[3] = fileTree->dirEntry.exec;
regs.r[5] = fileTree->dirEntry.attr & 0xff;
err = _kernel_swi( OS_File, ®s, ®s );
if( err )
{
fprintf( stderr, "%s\n", err->errmess );
}
}
#endif
}
break;
case xrecover_chunkCantOpen:
{
/* This chunk becomes one of the list of unrecovered chunks */
fileTree->status |= xrecover_openFail;
/* Make sure that we don't try to re-link this file with its chunk
*/
if( 0 == --(crossCheckTable[fileTree->node].references) )
{
crossCheckTable[fileTree->node].file = NULL;
}
/* OK. Not beautiful - ideally each chunk should have a linked list
* (or variable array - time for genArray.c to meet the outside
* world?) of files that (cross)link to it, but ideally there should
* be no cross links.
*
* If you'd like a copy of genArray.c, email <bagpuss@done.net>
*/
/* ++problemFiles; */
/* Don't need this, as *this file* wasn't the problem, rather it was
* the output media
*/
break;
}
free( outPathname );
}
}
/* Else there was no chunk found for this file, or we have already made an
* attempt to open it for output, and it has failed.
*/
fileTree->status |= xrecover_started;
/* Either we started it this run, or it was already started, so this makes
* no difference
*/
}
else ++problemFiles;
fileTree = fileTree->next;
}
return problemFiles;
}
static int xrecover_RecoverTree( FILE *in, const char *victim,
const char *outNameBuffer,
char *victimGoesHere,
unsigned chunkCount,
const xFiles_header *header,
const xFiles_chunk *chunkTable,
const unsigned long *rootDirOffset,
const unsigned maxChunk, BOOL dirsAsText,
BOOL suppressFiles, const BOOL useAlloc,
const int verbose )
{
unsigned rootDirChunk = header ? header->rootChunk : 0;
unsigned long useRootDirOffset;
xrecover_chunkTableCrossCheck *crossCheckTable;
xrecover_filenameListEntry *fileTree = 0;
unsigned loop;
unsigned problemFiles, unclaimedChunks = 0;
if( rootDirOffset == 0 )
{
if( rootDirChunk == 0 || chunkTable == 0 )
{
fputs( "Can't find root directory.\n", stderr );
return 1;
}
useRootDirOffset = ( chunkTable + rootDirChunk )->offset;
}
else
{
useRootDirOffset = *rootDirOffset;
rootDirChunk = xrecover_chunkFromOffset( chunkTable, maxChunk,
useRootDirOffset );
if( verbose > 1 )
{
printf( "Root dir offset %ld ", useRootDirOffset );
if( rootDirChunk )
{
printf( "corresponds to chunk %d\n", rootDirChunk );
}
else
{
puts( "does not correspond to any used chunk" );
}
}
}
if( fseek( in, useRootDirOffset, SEEK_SET ) )
{
fprintf( stderr, "Could not seek to root directory at %ld\n",
useRootDirOffset );
return 1;
}
else if( verbose > 1 )
{
printf( "Using root directory at %ld\n", useRootDirOffset );
}
if( 0 == ( crossCheckTable = malloc( sizeof( xrecover_chunkTableCrossCheck )
* maxChunk ) ) )
{
fprintf( stderr, "Failed to get %d bytes of memory for crossCheckTable\n",
sizeof( xrecover_chunkTableCrossCheck ) * maxChunk );
return 1;
}
for( loop = maxChunk; loop-- != 0; )
{
crossCheckTable[loop].file = NULL;
crossCheckTable[loop].status = xrecover_untouched;
}
xrecover_recoverDir( in, 0, victim, "", useRootDirOffset, rootDirChunk, 0,
chunkTable, crossCheckTable, &fileTree, maxChunk,
verbose );
/*
xrecover_printList( stdout, chunkTable, crossCheckTable, fileTree, verbose ); */
problemFiles = xrecover_doGoodInList( in, suppressFiles ? 0 : victim,
chunkTable, crossCheckTable,
fileTree, useAlloc, verbose );
for( loop = maxChunk; --loop > 0; )
/* 1 rather than 0 so that we miss the chunkTable chunk */
{
if( chunkTable[loop].usage != FREE && loop != rootDirChunk )
{
if( crossCheckTable[loop].file == NULL )
{
unclaimedChunks++;
}
else
{
test( crossCheckTable[loop].references == 1,
"Chunk %d has %d references\n", loop,
crossCheckTable[loop].references );
}
}
}
if( verbose > 0 )
{
if( problemFiles == 1 )
{
fputs( "There is one problem file", stdout );
}
else
{
printf( "There are %d problem files", problemFiles );
}
printf( " and %d unclaimed chunk%s\n", unclaimedChunks,
( unclaimedChunks == 1 ) ? "" : "s" );
}
if( problemFiles || unclaimedChunks )
{
/* Here goes. Let's try to match them up */
xrecover_filenameListEntry **problemFile;
const xFiles_chunk **unclaimedChunk, **currentChunk;
problemFile = malloc( sizeof( xrecover_filenameListEntry *)
* problemFiles );
unclaimedChunk = malloc( sizeof( xFiles_chunk ** ) * unclaimedChunks );
if( problemFile == 0 && unclaimedChunk == 0 )
{
fputs( "Could not get memory to sort problem files and unclaimed "
"chunks\n", stderr );
}
else
{
unsigned int lastSize = UINT_MAX;
currentChunk = unclaimedChunk;
/* Entry can never (reasonably) be this big as there will be some bytes
* used for the (header/chunkTable/dir) */
for( loop = maxChunk; --loop > 0 ; )
{
if( chunkTable[loop].usage != FREE && loop != rootDirChunk
&& crossCheckTable[loop].file == NULL )
{
*currentChunk++ = &(chunkTable[loop]);
}
}
qsort( (void *) unclaimedChunk, unclaimedChunks, sizeof( xFiles_chunk * ),
chunkSizeCmp );
xrecover_getProblemList( fileTree, problemFile, problemFiles );
qsort( problemFile, problemFiles, sizeof( xrecover_filenameListEntry * ),
filenameListEntryCmp );
test( currentChunk == unclaimedChunk + unclaimedChunks,
"Awooga - can't count - first time there were %d unclaimed chunks, "
"now there are %d\n", unclaimedChunks,
currentChunk - unclaimedChunk );
for( loop = problemFiles; loop-- > 0 ; )
{
const char *thisFile = ( (char *) &(problemFile[loop]->dirEntry) )
+ sizeof( xFiles_dirEntry );
const unsigned thisSize = problemFile[loop]->dirEntry.size;
unsigned index;
/* If this file has the same size as the previous file ignore it */
if( thisSize == lastSize ) continue;
/* If this file has the same size as the next file,
0: Make sure that the next file is ignored too
1: Ignore it
*/
if( loop > 0 && ( problemFile[loop-1]->dirEntry.size == thisSize ) )
{
lastSize = thisSize;
if( verbose > 0 )
{
printf( "As files '%s' and '%s' have same size (%d) "
"cannot attempt to match to unclaimed chunks\n", thisFile,
( (char *) &(problemFile[loop - 1]->dirEntry) )
+ sizeof( xFiles_dirEntry ), thisSize );
}
continue;
}
while( ( currentChunk-- > unclaimedChunk )
&& chunkTable[ index = *currentChunk - chunkTable ].size
> thisSize );
/* 8-). index = *currentChunk - chunkTable; somewhere in the middle of
* all that. Anyway, we get here when a chunk size matches or we run out
* of chunks
*/
if( currentChunk < unclaimedChunk /* Out of chunks */
|| chunkTable[index].size < thisSize )
/* index won't be used here before it is set */
{
if( verbose > 0 )
{
printf( "Cannot find chunk to match file '%s' size %d\n", thisFile,
thisSize );
}
}
else if( currentChunk > unclaimedChunk /* At least one more chunk */
&& chunkTable[ *( currentChunk - 1 ) - chunkTable ].size
== thisSize )
{
if( verbose > 0 )
{
printf( "More than one chunk to match file '%s' size %d\n",
thisFile, thisSize );
}
/* Skip past chunks of this size */
while( ( currentChunk-- > unclaimedChunk )
&& chunkTable[ *currentChunk - chunkTable ].size == thisSize );
}
else
{
/* ¡!¡Hit!¡!
* Exactly one file of this size matched exactly one chunk.
* Let's brag about it on stderr
*/
fprintf( stderr, "Chunk %d size %d matches file '%s' size %d\n"
"Will attempt recovery of this chunk as this file\n",
index, chunkTable[index].size, thisFile, thisSize );
/* Reset the flags so that a new attempt on recovery will be made */
problemFile[loop]->status |= xrecover_foundChunk
| xrecover_sizeMatches;
problemFile[loop]->status &= ~xrecover_started;
problemFile[loop]->node = index;
crossCheckTable[index].references = 1;
crossCheckTable[index].file = problemFile[loop];
}
}
}
free( problemFile );
free( (void *) unclaimedChunk );
/* Let's have another go */
problemFiles = xrecover_doGoodInList( in, suppressFiles ? 0 : victim,
chunkTable, crossCheckTable,
fileTree, useAlloc, verbose );
}
xrecover_freeList( &fileTree );
/* Don't need this anymore. Now go for the unclaimed chunks */
if( victim )
{
for( loop = maxChunk; --loop > 0 ; )
{
if( chunkTable[loop].usage == FREE || loop == rootDirChunk
|| crossCheckTable[loop].file != 0 )
continue;
/* Unreferenced, non-free chunk. Looks ripe for recovery */
if( victim )
{
sprintf( victimGoesHere, chkdskString, chunkCount++ );
}
/* outNameBuffer == 0 iff victim == 0 */
xrecover_recoverChunkChkdsk( in, outNameBuffer, loop,
chunkTable + loop,
useAlloc ? chunkTable[loop].allocSize
: 0,
dirsAsText, suppressFiles, verbose );
}
}
free( crossCheckTable );
return 0;
}
static int xrecover_RecoverFile( const char *problem, const char *victim,
const unsigned long *chunkTableOffset,
const unsigned long *rootDirOffset,
const xrecover_qualityLevel qualityOfJob,
const BOOL dirsAsText,
const BOOL canCreateVictim,
const BOOL suppressFiles, const BOOL writeFree,
const BOOL useAlloc, const int verbose )
{
/* Victim == 0 to suppress all writes */
FILE *in;
long problemSize;
size_t victimPathLen = victim ? strlen( victim ) + strlen( xrecover_dirSep )
: 0;
size_t outNameBufferLen = victim ? ( victimPathLen
+ strlen( chkdskString ) + 1 )
: 0;
char *outNameBuffer = victim ? malloc( outNameBufferLen ) : 0;
unsigned maxChunk;
unsigned readChunks;
unsigned chunkLoop;
unsigned chunkCount = 0;
unsigned long seekTo;
xFiles_header header;
xFiles_chunk *chunkTable;
xFiles_chunk rootChunk;
const xFiles_chunk **sortedChunkTable, **currentChunk, **endSorted;
unsigned long endLastChunk = 0;
#ifdef __riscos
_kernel_swi_regs regs;
_kernel_oserror *err;
int type;
#endif
#ifdef __riscos
if( victim )
{
regs.r[0] = 5;
regs.r[1] = (int) victim;
if( err = _kernel_swi( OS_File, ®s, ®s ), err )
{
fprintf( stderr, "%s\n", err->errmess );
return 1;
}
type = regs.r[0];
if( type == 0 && canCreateVictim )
{
if( verbose > 0 ) printf( "Creating x-file '%s'\n", victim );
regs.r[0] = 11;
regs.r[2] = xFiles_TYPE;
regs.r[4] = 0;
regs.r[5] = 0;
if( err = _kernel_swi( OS_File, ®s, ®s ), err )
{
fprintf( stderr, "%s\n", err->errmess );
return 1;
}
}
else if( 0 == ( type & 2 ) )
{
/* Use OS File to make a message */
regs.r[0] = 19;
regs.r[2] = type;
err = _kernel_swi( OS_File, ®s, ®s );
if( !err ) err = (_kernel_oserror *) regs.r[0];
/* Get the generated error message if no erroneous error occurred */
fprintf( stderr, "%s\n", err->errmess );
return 1;
}
}
#endif /* end of Risc OS specific bits */
/* hopefully from now on is ANSI */
if( victim )
{
if( 0 == outNameBuffer )
{
fprintf( stderr, "Failed to get %d bytes of memory for outNameBuffer\n",
outNameBufferLen );
return 1;
}
sprintf( outNameBuffer, "%s%s", victim, xrecover_dirSep );
}
if( 0 == ( in = fopen( problem, "rb" ) ) )
{
fprintf( stderr, "Could not open file '%s'\n", problem );
free( outNameBuffer );
return 1;
}
if( fseek( in, 0, SEEK_END ) || ( -1L == ( problemSize = ftell( in ) ) ) )
{
fprintf( stderr, "Could not find size of file '%s'\n", problem );
free( outNameBuffer );
return 1;
}
rewind( in );
if( 0 == fread( &header, sizeof( header ), 1, in ) )
{
fprintf( stderr, "Could not read header from file '%s'\n", problem );
free( outNameBuffer );
return 1;
}
test( header.sig == xFiles_SIG,
"Missing xFiles_SIG - read %8X, should be %8X\n",
header.sig, xFiles_SIG );
if( verbose > 1 )
{
test( header.hdrSize == sizeof( xFiles_header ),
"Illegal hdrSize %d\n",
header.hdrSize );
test( header.structureVersion == xFiles_STRUCTUREVERSION,
"Illegal structureVersion %d\n",
header.structureVersion );
test( header.directoryVersion == xFiles_DIRECTORYVERSION,
"Illegal directoryVersion %d\n",
header.directoryVersion );
/* testChunk(&info, 0, & header.chunkTable ); */
test( power2( header.allocationUnit ),
"Illegal allocationUnit %d\n", header.allocationUnit );
}
/*
test( ( roundUp( &header, sizeof( header ) ) == header.chunkTable.offset ),
"chunkTable not found 1 allocationUnit into file\n"
"Expected %d\n"
"Actually %d\n", roundUp( &header, sizeof( header ) ),
header.chunkTable.offset ); */
seekTo = chunkTableOffset ? *chunkTableOffset : header.chunkTable.offset;
if( fseek( in, seekTo, SEEK_SET ) )
{
fprintf( stderr, "Could not seek to chunkTable at %ld\n", seekTo );
free( outNameBuffer );
return 1;
}
;
if( 0 == fread( &rootChunk, sizeof( xFiles_chunk ), 1, in ) )
{
fprintf( stderr,
"Could not read first chunk of chunkTable from file '%s'\n",
problem );
free( outNameBuffer );
return 1;
}
/* Derive size from header, unless chunkTable offset was specified */
maxChunk = ( chunkTableOffset ? rootChunk.size
: header.chunkTable.size )
/ sizeof( xFiles_chunk );
test( 0 == ( header.chunkTable.size % sizeof( xFiles_chunk ) ),
"chunkTable size %d (from header) is not a multiple of %d\n",
header.chunkTable.size, sizeof( xFiles_chunk ) );
test( 0 == ( rootChunk.size % sizeof( xFiles_chunk ) ),
"chunkTable size %d (from chunkTable) is not a multiple of %d\n",
rootChunk.size, sizeof( xFiles_chunk ) );
if( 0 == ( chunkTable = malloc( header.chunkTable.size ) ) )
{
fprintf( stderr, "Failed to get %d bytes of memory for chunkTable\n",
header.chunkTable.size );
fclose( in );
free( outNameBuffer );
return 1;
}
if( 0 == ( sortedChunkTable = malloc( sizeof( xFiles_chunk ** ) * maxChunk ) ) )
{
fprintf( stderr, "Failed to get %d bytes of memory to sort chunkTable\n",
sizeof( xFiles_chunk ** ) * maxChunk );
free( chunkTable );
free( outNameBuffer );
fclose( in );
return 1;
}
if( verbose > 0 )
{
printf( "'%s' has %d chunk(s)\n", problem, maxChunk );
}
*chunkTable = rootChunk;
readChunks = fread( chunkTable + 1, sizeof( xFiles_chunk ), maxChunk - 1,
in );
if( readChunks < maxChunk - 1 )
{
fprintf( stderr,
"Only read %d chunks out of %d allegedly in chunkTable\n"
"Will only deal with these %d chunkss\n",
readChunks + 1, maxChunk, readChunks + 1);
maxChunk = readChunks + 1;
}
for( chunkLoop = maxChunk ; chunkLoop-- > 0;
sortedChunkTable[chunkLoop] = &(chunkTable[chunkLoop]) );
qsort( (void *) sortedChunkTable, maxChunk, sizeof( xFiles_chunk * ),
chunkOffsetCmp );
if( verbose > 1 )
{
puts( "Chunk Offset Size Usage Allocated" );
/* Puts adds \n. Isn't the ANSI C library wonderfully consistent:
* puts( a ) == fputs( a + '\n', stdout ) == fprintf( stdout, "%s\n", a ) */
}
/* OK. Go through these in offset order */
for( currentChunk = sortedChunkTable, endSorted = sortedChunkTable + maxChunk;
currentChunk < endSorted; currentChunk++ )
{
chunkLoop = *currentChunk - chunkTable;
if( chunkTable[chunkLoop].usage == FREE )
{
if( verbose > 4 )
{
printf( " %4d %7d FREE\n", chunkLoop,
chunkTable[chunkLoop].offset );
}
test( chunkTable[chunkLoop].offset < maxChunk,
"Chunk %d (free) bad offset %08x\n", chunkLoop,
chunkTable[chunkLoop].offset );
test( chunkTable[chunkLoop].size == FREE,
"Chunk %d (free) size is %08x (should be %08x)\n",
chunkLoop, chunkTable[chunkLoop].size, FREE );
test( chunkTable[chunkLoop].allocSize == FREE,
"Chunk %d (free) allocSize is %08x (should be %08x)\n",
chunkLoop, chunkTable[chunkLoop].allocSize, FREE );
}
else
{
test( okOffset( &header, chunkTable[chunkLoop].offset ),
"Chunk %d (used) bad offset %08x\n", chunkLoop,
chunkTable[chunkLoop].offset );
if( endLastChunk != -1L )
{
/* offset is unsigned - if it's 0 for start of file then offset can
*never* be less than endLastChunk */
if( chunkTable[chunkLoop].offset < endLastChunk )
{
fprintf( stderr, "Chunk %d offset %d overlaps end of previous chunk"
" (%d, ends at %ld)\n", chunkLoop,
chunkTable[chunkLoop].offset,
*( currentChunk - 1 ) - chunkTable, endLastChunk );
}
else if( chunkTable[chunkLoop].offset > endLastChunk )
{
if( verbose > 1 )
{
printf( "%ld byte(s) %s between ",
chunkTable[chunkLoop].offset - endLastChunk,
endLastChunk ? "free space" : "x-file header" );
if( endLastChunk )
{
printf( "chunk %d", *( currentChunk - 1 ) - chunkTable );
}
else
{
fputs( "start of file", stdout ); /* puts appends '\n' */
}
printf( " and chunk %d\n", chunkLoop );
}
if( writeFree && victim )
{
sprintf( outNameBuffer + victimPathLen, chkdskString,
chunkCount++ );
xrecover_recoverArbitaryArea( in, outNameBuffer, endLastChunk,
chunkTable[chunkLoop].offset
- endLastChunk, verbose );
}
}
}
if( verbose > 1 )
{
printf( " %4d %7.d %7.d %7.d %9.d\n", chunkLoop,
chunkTable[chunkLoop].offset, chunkTable[chunkLoop].size,
chunkTable[chunkLoop].usage, chunkTable[chunkLoop].allocSize );
}
test( (chunkTable[chunkLoop].size <= chunkTable[chunkLoop].allocSize ),
"Chunk %d (used) bad size %08x (allocSize %08x)\n",
chunkLoop, chunkTable[chunkLoop].size,
chunkTable[chunkLoop].allocSize );
test( okOffset( &header, chunkTable[chunkLoop].allocSize ),
"Chunk %d (used) bad allocSize %08x\n", chunkLoop,
chunkTable[chunkLoop].allocSize );
test( ( problemSize > chunkTable[chunkLoop].offset ),
"Chunk %d (used) offset %08x > size of '%s' (%08x)\n", chunkLoop,
chunkTable[chunkLoop].offset, problem, problemSize );
endLastChunk = chunkTable[chunkLoop].offset
+ chunkTable[chunkLoop].allocSize;
/* Whatever the method, recover the chunktable here if useAlloc is set */
if( qualityOfJob == xrecover_qualityChkdsk
|| ( chunkLoop == 0 && useAlloc ) )
{
if( victim )
{
sprintf( outNameBuffer + victimPathLen, chkdskString, chunkCount++ );
}
/* outNameBuffer == 0 iff victim == 0 */
xrecover_recoverChunkChkdsk( in, outNameBuffer, chunkLoop,
chunkTable + chunkLoop,
useAlloc ? chunkTable[chunkLoop].allocSize
: 0,
dirsAsText, suppressFiles, verbose );
}
}
}
if( endLastChunk != -1L && problemSize > endLastChunk )
{
/* offset is unsigned - if it's 0 for start of file then offset can
*never* be less than endLastChunk */
if( verbose > 1 )
{
printf( "%ld byte(s) free space between chunk %d and end of file\n",
problemSize - endLastChunk, *( currentChunk - 1 ) - chunkTable );
}
if( writeFree && victim )
{
sprintf( outNameBuffer + victimPathLen, chkdskString, chunkCount++ );
xrecover_recoverArbitaryArea( in, outNameBuffer, endLastChunk,
problemSize - endLastChunk, verbose );
}
}
switch( qualityOfJob )
{
case xrecover_qualityChkdsk:
break; /* Done this while checking chunk table */
case xrecover_qualityReadDirs:
{
xrecover_RecoverTree( in, victim, outNameBuffer,
outNameBuffer + victimPathLen, chunkCount,
&header, chunkTable, rootDirOffset,
maxChunk, dirsAsText, suppressFiles, useAlloc,
verbose );
}
break;
default:
fprintf( stderr, "Unimplemented quality %d\n", qualityOfJob );
}
free( sortedChunkTable );
free( chunkTable );
free( outNameBuffer );
/* Some systems will memory leak if one doesn't do this */
/* (Not freeing things is a Vaxism (and Unixism) ) */
return 0;
}
int main( int argc, char *argv[] )
{
int argn;
int verbose = 0;
const char *problem = 0; /* aka x-file to fix */
const char *victim = 0; /* aka destination 'directory' */
BOOL dirsAsText = TRUE;
BOOL suppressOutput = FALSE;
BOOL canCreateVictim = FALSE;
BOOL suppressFiles = FALSE;
BOOL xguess = FALSE;
BOOL useAlloc = FALSE;
BOOL writeFree = FALSE;
xrecover_Offset rootDir = { FALSE, 0 };
xrecover_Offset chunkTable = { FALSE, 0 };
xrecover_qualityLevel qualityOfJob = xrecover_qualityReadDirs;
#ifdef __riscos
#ifdef __GNUC__
__uname_control |= __UNAME_NO_PROCESS;
#endif
#endif
for( argn = 1; ( argn < argc ) && ( *argv[argn] == '-' ); argn++ )
{
const char *current = argv[argn] + 1;
if( *current == '-' )
{
argn++;
break; /* Closet goto out of the for loop */
/* -- marks end of options à la Unix */
}
while( current && *current )
{
switch( tolower( *current ) )
{
case 'a':
useAlloc = TRUE;
break;
#ifdef __riscos
case 'c':
canCreateVictim = TRUE;
break;
#endif
case 'd':
dirsAsText = FALSE;
break;
case 'f':
writeFree = TRUE;
break;
case 'g':
xguess = TRUE;
case 'n':
suppressOutput = TRUE;
break;
case 's':
suppressFiles = TRUE;
break;
case 'v':
verbose += 1;
break;
case 'r':
if( *(current + 1) )
{
if( FALSE == xrecover_ReadOffset( &rootDir, current + 1 ) )
{
argn = argc + 1;
}
}
else
{
argn = xrecover_ReadOffset( &rootDir, argv[argn+1] ) ? argn + 1
: argc + 1;
}
current = 0; current--;
/* Whatever happens, skip on to next argv */
break;
/* If TRUE, value is successfully read, so skip over value
* Else argn = argc will break out of this loop
* AND trigger syntax message */
case 't':
if( *(current + 1) )
{
if( FALSE == xrecover_ReadOffset( &chunkTable, current + 1 ) )
{
argn = argc + 1;
}
}
else
{
argn = xrecover_ReadOffset( &chunkTable, argv[argn+1] ) ? argn + 1
: argc + 1;
}
current = 0; current--;
/* Whatever happens, skip on to next argv */
break;
case '0':
qualityOfJob= xrecover_qualityEvery1024;
break;
case '1':
qualityOfJob= xrecover_qualityChkdsk;
break;
case '2':
qualityOfJob= xrecover_qualityReadDirs;
break;
case 'm':
if( *(current + 1) == '$' )
{
chkdskString = chkdskStringMS;
current++;
break;
} /* Else fall through to this: */
default:
fprintf( stderr, "Ignoring unknown option '%c'\n", *current );
break;
}
current++;
} /* while( current && *current ) */
}
if( argn < argc )
{
problem = argv[argn++];
}
if( argn < argc )
{
victim = argv[argn++];
}
if( ( ( victim == 0 ) && !suppressOutput ) || ( argn != argc ) )
{
/* When is Acorn's F***ing Run time going to do tabs properly? */
/* Use gcc - free software always offers better value for money */
fputs( "Syntax:\tx-recover [options] <x-file> <dest-dir>\n"
"\t\t-v for verbose info (more v's for more verbosity!)\n"
#ifdef __riscos
"\t\t-c to create dest-dir as an x-file if necessary\n"
#endif
"\t\t-d To output directories in raw binary form\n"
"\t\t-g To scan file to find the chunk table and root directory\n"
"\t\t (implies -n)\n"
"\t\t-n To suppress all disc output (still report integrity checks)"
"\n"
"\t\t-r <offset> to specify the offset of the root directory\n"
"\t\t-s To suppress all file output (but create directory tree)\n"
"\t\t-t <offset> to specify the offset of the chunkTable\n"
"\t\t-a To round up all file sizes to the size of the chunk\n"
"\t\t-f To write out all free space between chunks as files\n"
/* "\t\t-e To round up the x-file to the size allocated on disc\n" */
/* nasty filing system fills it with zeros, so not much point. I don't think
* ADFS did this on Risc OS 2 (Network filing systems did, being security
* concious */
/* "\t\t-0 Attempt to split chunks ignoring chunk table\n" */
"\t\t-1 Output all used chunks as files (needs intact chunk table)\n"
"\t\t-2 Attempt to traverse the directory tree and recreate the \n"
"\t\t x-file (default method)\n",
stderr );
return 1;
}
if( xguess ) return xrecover_xguess( problem );
if( verbose > 0 )
{
if( victim )
{
printf( "%s -> %s (v=%d)\n", problem, victim, verbose );
}
else
{
printf( "%s [suppress output] (v=%d)\n", problem, verbose );
}
if( rootDir.valid )
{
printf( "Read root directory from file offset %ld\n", rootDir.offset );
}
if( chunkTable.valid )
{
printf( "Read chunk table from file offset %ld\n", chunkTable.offset );
}
}
return xrecover_RecoverFile( problem, suppressOutput ? 0 : victim,
chunkTable.valid ? &(chunkTable.offset) : 0,
rootDir.valid ? &(rootDir.offset) : 0,
qualityOfJob, dirsAsText,
canCreateVictim, suppressFiles, writeFree,
useAlloc, verbose );
}